Improved support for OpenRouter - #22
Merged
Merged
Conversation
defaultModelForHarness fell back to a provider-blind constant whenever no model was pinned. Provider availability was only ever used to reject a model, never to choose one, so a deployment holding only an OpenRouter or OpenAI key got an Anthropic default and refused every turn while check, plan, doctor and up all passed. The fallback is now provider-aware at the one function all eight resolution paths flow through. MODEL_PROVIDER reaches core as real configuration rather than only a secret gate, and availability is intersected with what the harness can route. With no declaration the keys present decide, which fixes the same defect for deployments that predate modelProvider. Harness and provider pairs that can never serve a turn, and a model pin from another vendor, are refused at config load in both the CLI and core instead of at the first message. Two invariants the base branch held together with comments are gone with the thing they explained: duplicate-name secret specs collapse to one per name via an any condition and an optionalOtherwise flag, so array position no longer decides a description, and requiredWhen takes a list, retiring a dedupe pass and an if-chain fallthrough that treated any unmatched gate as the Linear one. Both catalogs now have uniqueness tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codex challenged the diff and found two ways it could hurt a live stack. Inferring the base-model provider from whichever API keys happen to be in the environment silently moved an existing deployment, and its billing: a stack holding OPENROUTER_API_KEY as the documented optional fallback while actually running Anthropic from the Admin page would flip to openrouter/auto on upgrade. The inference is gone. Without MODEL_PROVIDER the shipped default stands exactly as before, so no deployment moves unless it says to. Refusing a cross-vendor model pin at boot could brick a rollout the CLI had already approved, because the CLI holds no model registry and could not mirror the check. That throw is gone too; a pin wins, as it always has. What remains is the harness/provider rule, which the CLI does mirror and a drift test pins to the registry. The CLI now reads the provider core will actually use, so an env.core.MODEL_PROVIDER override is validated rather than skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
16francej
force-pushed
the
openrouter-base-model
branch
from
July 30, 2026 06:14
254d1ad to
37e28a1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on
model-provider-key-in-deploy.A deployment that named OpenRouter (or OpenAI) as its model provider passed every deployment gate and then refused every message. The provider a deployment declares now selects the model it runs.
Change explainer: https://claude.ai/code/artifact/553b7a30-63bd-4f4b-931a-c75f6d2424c0
Root cause
defaultModelForHarnessfell back to a provider-blind constant —claude-opus-5— whenever no model was pinned. Provider availability was computed separately and used only to reject a model, never to choose one. So an OpenRouter-only deployment got an Anthropic default and every turn died on "that model isn't available on this deployment".check,plan,doctorandupwere all green, because the OpenRouter key itself was fine.The change
MODEL_PROVIDERreaches core as real configuration rather than only a secret gate.modelProvider, the shipped default stands exactly as before, so upgrading never changes an existing deployment's model or its billing.env.core.MODEL_PROVIDERoverride is checked rather than skipped.qm init --model-provider openrouterscaffolds a config that works unedited.Incidental
Two invariants in the base branch were held together by comments, against the repo's zero-comment standard; both are gone because the thing they explained is gone. Duplicate-name secret specs collapse to one per name (an
anycondition plus anoptionalOtherwiseflag), so array position no longer decides a secret's description.requiredWhentakes a list, retiring a dedupe pass and an if-chain fallthrough that treated any unmatched gate as the Linear one. Both catalogs gained uniqueness tests.Verification
Two cores booted from this branch and its base, both
HARNESS=pi MODEL_PROVIDER=openrouterwith an OpenRouter key as the only credential:runtime-config.effective.modelIdclaude-opus-5openrouter/autoPOST /v1/turnsThe turn now fails only on the deliberately fake key (
401 Missing Authentication header) — no real OpenRouter credential was available, so the provider round-trip is the one step not exercised end to end.qm init --model-provider openrouterandqm checkwere driven through the real CLI, including the new rejection ofopenrouter+HARNESS=codex.CI was green on the first commit (14/14). CLI suite 484/484 locally;
tscclean on core, CLI and contract projects;eslint,oxlint --deny-warnings,prettier --check,knipclean. The root suite has a handful of local failures that are pre-existing timing flakes — the failing subset differs run to run, and the base branch fails the same files with a different subset.Review
Three independent reviewers plus an adversarial Codex pass ran against the diff before it landed. Seven confirmed problems, all resolved. Four things were cut rather than repaired — each fix made the change smaller:
qm checkhad already approved.surface-configmodel picker. It inverted an admin's explicit narrowing into a widening, because an emptied list falls through to every model. The picker the web UI actually reads already filters correctly.Also fixed: a dropped de-duplication that listed two secrets twice in
.env.example, and anenv.core.MODEL_PROVIDERoverride that skipped CLI validation.Known gap, deliberately not fixed here: a deployment that defers its key to the Admin page still needs a base model picked there by hand, and a durable selection outranks the declared provider. Both predate this change;
deployment.mdnow says so instead of implying otherwise. The right fix is in runtime resolution — treat a stored selection whose provider is unavailable as absent — which also covers a key removed at redeploy.🤖 Generated with Claude Code